package cn.yhsx.jenkins.plugins.rotator;

import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.util.FormValidation;
import jakarta.servlet.ServletException;
import jenkins.tasks.SimpleBuildWrapper;
import net.sf.json.JSONObject;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

import javax.annotation.CheckForNull;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Initialize the data list and insert data into the environment
 * <li>1.read last config from  the current state file ,file default in `BUILD_SPACE/.rotator-state.json`</li>
 * <li>2.get current config</li>
 * <li>3.save data and params</li>
 *
 * @author slf
 * @since 2021/12/30 16:44
 */
public class RotatorWrapper extends SimpleBuildWrapper {

    public static String STATE_FILE = ".rotator-state.json";
    public static String CURRENT_DATA = "ROTATOR_CURRENT_DATA";
    public static String LAST_DATA = "ROTATOR_LAST_DATA";
    private String range;

    private Integer start;
    private boolean rollback;

    @DataBoundConstructor
    public RotatorWrapper(@CheckForNull String range) {
        this.range = range;
    }

    public static FilePath getStateFilePath(Run<?, ?> build) {
        FilePath job = new FilePath(build.getRootDir()).getParent().getParent();
        return job.getParent().child(job.getName()).child("workspace").child(STATE_FILE);
    }

    public static FilePath getBuildStateFilePath(Run<?, ?> build) {
        return new FilePath(build.getRootDir()).child(STATE_FILE);
    }

    public static boolean isInvalidRange(String range) {

        String regex = "^(\\d+(~\\d+)?|\\w+(,\\w+)*)$";

        Pattern pattern = Pattern.compile(regex);
        if (pattern.matcher(range).matches()) {
            return false;
        }
        regex = "^(\\w*\\[(\\d+(~\\d+)?|\\w+(,\\w+)*)]\\w*)$";
        pattern = Pattern.compile(regex);
        return !pattern.matcher(range).matches();
    }

    public String getRange() {
        return range;
    }

    public void setRange(String range) {
        this.range = range;
    }

    public Integer getStart() {
        return start;
    }

    @DataBoundSetter
    public void setStart(Integer start) {
        this.start = start;
    }

    public boolean isRollback() {
        return rollback;
    }

    @DataBoundSetter
    public void setRollback(boolean rollback) {
        this.rollback = rollback;
    }

    @Override
    public void setUp(Context context, Run<?, ?> build, FilePath workspace,
                      Launcher launcher, TaskListener listener, EnvVars env) {
        try {
            // 1. 读取文件夹配置
            RotatorConfig lastConfig = readLastConfig(build, listener);

            RotatorConfig config = getCurrentConfig(lastConfig, build, listener);

            // 3. 存储当前配置到Action
            RotatorAction action = new RotatorAction(config, lastConfig);
            build.addAction(action);

            listener.getLogger().println("Selected data: " + action.getCurrentData());

        } catch (RotatorException e) {
            listener.error(e.getMessage());
            build.setResult(Result.FAILURE);
        } catch (Exception e) {
            listener.error(Arrays.toString(e.getStackTrace()));
            build.setResult(Result.FAILURE);
        }
    }

    private RotatorConfig getCurrentConfig(RotatorConfig lastConfig, Run<?, ?> build,
                                           TaskListener listener)
            throws RotatorException, InvocationTargetException, IllegalAccessException, IOException, InterruptedException {
        RotatorConfig config;
        EnvVars env = build.getEnvironment(listener);
        if (build instanceof AbstractBuild) {
            env.overrideAll(((AbstractBuild<?, ?>) build).getBuildVariables());
        }

        String range = env.expand(this.range);
        if (isInvalidRange(range)) {
            throw new RotatorException(Messages.RotatorWrapper_error_range_errorFormat());
        }
        if (lastConfig == null) {
            config = new RotatorConfig(range, null);
        } else {
            if (!lastConfig.getRange().equals(range)) {
                RotatorConfig newConfig = new RotatorConfig(range, lastConfig.getParams());
                int fi = newConfig.getDataList().indexOf(lastConfig.getCurrentData());
                if (fi > -1) {
                    newConfig.setCurrent(fi);
                }
                config = newConfig;
            } else {
                config = new RotatorConfig();
                BeanUtils.copyProperties(config, lastConfig);
            }
            setCurrent(config, config.getCurrent() + 1);
        }
        config.setBuildId(build.getId());

        if (start != null) {
            setCurrent(config, start);
        }

        if (rollback) {
            if (StringUtils.isBlank(config.getLastBuildId())) {
                throw new RotatorException(Messages.RotatorWrapper_error_rollback_NoPrevious());
            }
            Run<?, ?> lastBuild = build.getParent().getBuild(config.getLastBuildId());
            config = readConfig(getBuildStateFilePath(lastBuild));
            if (config == null) {
                throw new RotatorException(
                        Messages.RotatorWrapper_error_rollback_ReadPreviousError() + lastBuild.getId() + "-"
                                + lastBuild.getParent().getFullName() + "-" + lastBuild.getNumber());
            }
        }
        return config;
    }

    private RotatorConfig readLastConfig(Run<?, ?> build, TaskListener listener)
            throws IOException, InterruptedException {
        FilePath statePath = getStateFilePath(build);
        return readConfig(statePath);
    }

    private RotatorConfig readConfig(FilePath statePath) throws IOException, InterruptedException {
        if (!statePath.exists()) {
            return null;
        }
        String content = statePath.readToString().trim();
        if (StringUtils.isBlank(content)) {
            return null;
        }
        RotatorConfig config = (RotatorConfig) JSONObject.fromObject(content)
                .toBean(RotatorConfig.class);
        if (!config.getParams().isEmpty()) {
            Map<String, Map<String, String>> params = new HashMap<>();
            for (String data : config.getDataList()) {
                Map<String, String> param = (Map<String, String>) JSONObject.fromObject(
                        config.getParams().get(data)).toBean(Map.class);
                params.put(data, param);
            }
            config.setParams(params);
        }
        return config;
    }

    private void setCurrent(RotatorConfig config, Integer current) throws RotatorException {
        if (config.getDataList() == null || config.getDataList().isEmpty()) {
            throw new RotatorException("No data defined");
        }
        config.setCurrent(current % config.getDataList().size());
    }

    @Extension
    @Symbol("rotator")
    public static final class RotatorDescriptorImpl extends BuildWrapperDescriptor {

        public FormValidation doCheckRange(@QueryParameter String range)
                throws IOException, ServletException {
            if (range.startsWith("${") && range.endsWith("}")) {
                return FormValidation.ok();
            }
            if (isInvalidRange(range)) {
                return FormValidation.error(Messages.RotatorWrapper_error_range_errorFormat());
            }
            return FormValidation.ok();
        }

        @Override
        public boolean isApplicable(AbstractProject<?, ?> item) {
            return true;
        }

        @Override
        public String getDisplayName() {
            return Messages.RotatorWrapper_displayName();
        }


    }

}